﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using JT808.Gateway.Abstractions;
using JT808.Gateway.Abstractions.Configurations;
using JT808.Gateway.Session;
using JT808.Protocol;
using JT808.Protocol.Enums;
using JT808.Protocol.Extensions;
using JT808.Protocol.MessageBody;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Polly;

namespace JT808.Gateway.QueueServer.Custom
{
    public class JT808WebApiService : IJT808WebApiService
    {
        /// <summary>
        /// 为请求分配的消息订阅id及其状态
        /// id生成方式为终端号码+请求消息id+请求消息流水号
        /// 状态：0-已发送 1-已响应 2-超时
        /// </summary>
        public ConcurrentDictionary<string, byte> SubscribeIds { get; set; }
        /// <summary>
        /// 回调信息（可选）
        /// </summary>
        public ConcurrentDictionary<string, CallbackContext> Callbacks { get; set; }
        /// <summary>
        /// 收到回复时，将响应写入消息中间件
        /// </summary>
        public IJT808ApiResponseProducer Producer { get; set; }

        private ILogger Logger { get; set; }
        JT808SessionManager SessionManager;
        JT808Serializer JT808Serializer { get; }
        IOptions<JT808Configuration> options { get; set; }
        IJT808Config config { get; set; }
        HttpClient HttpClient { get; set; }
        /// <summary>
        /// id, json, package生成器工厂
        /// </summary>
        protected Dictionary<ushort, MsgIdMethodDelegate> IdAndJsonFactory { get; }
        /// <summary>
        /// 生成器
        /// </summary>
        /// <param name="sim">终端卡号</param>
        /// <param name="package">如果有headerPackage参数，则反序列化出完整package，否则为null</param>
        /// <param name="response">如果有headerPackage参数，则将package转换为json对象的二进制字节串，否则为null</param>
        /// <param name="msgId">平台请求消息的消息id，可为空</param>
        /// <param name="msgNum">平台请求消息的序列号，可为空</param>
        /// <param name="headerPackage">终端响应的头部反序列化对象</param>
        /// <returns>根据一定规则生成的订阅id</returns>
        protected delegate string MsgIdMethodDelegate(string sim, out JT808Package package, out byte[] response, ushort? msgId = null, ushort? msgNum = null, JT808HeaderPackage headerPackage = null);

        public ConcurrentDictionary<string, byte[]> CachedResponse { get; set; } = new();

        public JT808WebApiService(IJT808ApiResponseProducer responseProducer, ILoggerFactory loggerFactory,
            JT808SessionManager jT808SessionManager, IJT808Config jT808Config, IOptions<JT808Configuration> conf,
            HttpClient client)
        {
            Producer = responseProducer;
            Logger = loggerFactory.CreateLogger<JT808WebApiService>();
            SessionManager = jT808SessionManager;
            JT808Serializer = jT808Config.GetSerializer();
            options = conf;
            SubscribeIds = new();
            Callbacks = new();
            IdAndJsonFactory = new Dictionary<ushort, MsgIdMethodDelegate>
            {
                {JT808MsgId._0x0107.ToUInt16Value(),  Msg0x0107}
            };
            HttpClient = client;
            _ = TimeoutClearTask(TimeSpan.FromSeconds(300));
        }

        /// <summary>
        /// 定期清除已过期的订阅
        /// </summary>
        /// <param name="time">清除周期</param>
        /// <returns></returns>
        private async Task TimeoutClearTask(TimeSpan time)
        {
            while (true)
            {
                await Task.Delay(time).ContinueWith(task =>
                {
                    SubscribeIds.Where(pair => pair.Value == 2).Select(pair => pair.Key).ToList().ForEach(key =>
                    {
                        SubscribeIds.TryRemove(key, out var _);
                    });
                });
            }
        }

        private string Msg0x0107(string sim, out JT808Package package, out byte[] response, ushort? msgId = null, ushort? msgNum = null, JT808HeaderPackage headerPackage = null)
        {
            response = null;
            package = null;
            if (headerPackage != null)
            {
                package = JT808Serializer.Deserialize(headerPackage.OriginalData);
                var body = package.Bodies as JT808_0x0107;
                #region jsonfy
                MemoryStream stream = new MemoryStream();
                Utf8JsonWriter writer = new Utf8JsonWriter(stream, new JsonWriterOptions
                {
                    Indented = true,
                    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
                });
                writer.WriteStartObject();
                writer.WriteStartObject("终端类型");
                ReadOnlySpan<char> terminalTypeBits = string.Join("", Convert.ToString(body.TerminalType, 2).PadLeft(16, '0').Reverse()).AsSpan();
                writer.WriteString("bit0", terminalTypeBits[0] == '0' ? "不适用客运车辆" : "适用客运车辆");
                writer.WriteString("bit1", terminalTypeBits[1] == '0' ? "不适用危险品车辆" : "适用危险品车辆");
                writer.WriteString("bit2", terminalTypeBits[2] == '0' ? "不适用普通货运车辆" : "适用普通货运车辆");
                writer.WriteString("bit3", terminalTypeBits[3] == '0' ? "不适用出租车辆" : "适用出租车辆");
                writer.WriteString("bit6", terminalTypeBits[6] == '0' ? "不支持硬盘录像" : "支持硬盘录像");
                writer.WriteString("bit7", terminalTypeBits[7] == '0' ? "一体机" : "分体机");
                writer.WriteEndObject();
                writer.WriteString($"制造商ID", body.MakerId);
                writer.WriteString($"终端型号", body.TerminalModel);
                writer.WriteString($"终端ID", body.TerminalId);
                writer.WriteString($"终端SIM卡ICCID", body.Terminal_SIM_ICCID);
                writer.WriteNumber($"终端硬件版本号长度", body.Terminal_Hardware_Version_Length);
                writer.WriteString($"终端硬件版本号", body.Terminal_Hardware_Version_Num);
                writer.WriteNumber($"终端硬件版本号长度", body.Terminal_Firmware_Version_Length);
                writer.WriteString($"终端固件版本号", body.Terminal_Firmware_Version_Num);
                ReadOnlySpan<char> gNSSModuleBits = string.Join("", Convert.ToString(body.GNSSModule, 2).PadLeft(8, '0').Reverse()).AsSpan();
                writer.WriteNumber($"[{body.GNSSModule.ReadNumber()}]GNSS模块属性", body.GNSSModule);
                writer.WriteStartObject("GNSS模块属性");
                writer.WriteString("bit0", gNSSModuleBits[0] == '0' ? "不支持GPS定位" : "支持GPS定位");
                writer.WriteString("bit1", gNSSModuleBits[1] == '0' ? "不支持北斗定位" : "支持北斗定位");
                writer.WriteString("bit2", gNSSModuleBits[2] == '0' ? "不支持GLONASS定位" : "支持GLONASS定位");
                writer.WriteString("bit3", gNSSModuleBits[3] == '0' ? "不支持Galileo定位" : "支持Galileo定位");
                writer.WriteEndObject();
                ReadOnlySpan<char> communicationModuleBits = string.Join("", Convert.ToString(body.CommunicationModule, 2).PadLeft(8, '0').Reverse()).AsSpan();
                writer.WriteNumber($"[{body.CommunicationModule.ReadNumber()}]通信模块属性", body.CommunicationModule);
                writer.WriteStartObject("通信模块属性");
                writer.WriteString("bit0", communicationModuleBits[0] == '0' ? "不支持GPRS通信" : "支持GPRS通信");
                writer.WriteString("bit1", communicationModuleBits[1] == '0' ? "不支持CDMA通信" : "支持CDMA通信");
                writer.WriteString("bit2", communicationModuleBits[2] == '0' ? "不支持TD-SCDMA通信" : "支持TD-SCDMA通信");
                writer.WriteString("bit3", communicationModuleBits[3] == '0' ? "不支持WCDMA通信" : "支持WCDMA通信");
                writer.WriteString("bit4", communicationModuleBits[4] == '0' ? "不支持CDMA2000通信" : "支持CDMA2000通信");
                writer.WriteString("bit5", communicationModuleBits[5] == '0' ? "不支持TD-LTE通信" : "支持TD-LTE通信");
                writer.WriteString("bit6", "保留");
                writer.WriteString("bit7", communicationModuleBits[7] == '0' ? "不支持其他通信方式" : "不支持其他通信方式");
                writer.WriteEndObject();
                writer.WriteEndObject();
                writer.Flush();
                response = stream.ToArray();
                var hex = response.ToHexString();
                #endregion
                return package.Header.TerminalPhoneNo + 0x8107;
            }
            else if (msgId != null)
                return sim + msgId;
            throw new NotSupportedException("Neither [msgId] nor [package] is given!");
        }

        // 触发Webhook回调
        private async Task TriggerCallbackAsync(Uri callbackUrl, byte[] response)
        {
            var json = Encoding.UTF8.GetString(response);
            try
            {
                var content = new StringContent(json, Encoding.UTF8, "application/json");

                // 带重试的HTTP调用（使用Polly策略）
                var policy = Policy
                    .Handle<HttpRequestException>()
                    .WaitAndRetryAsync(3, retryAttempt =>
                        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

                var res = await policy.ExecuteAsync(async () =>
                    await HttpClient.PostAsync(callbackUrl, content));

                res.EnsureSuccessStatusCode();
                Logger.LogInformation("回调成功：{CallbackUrl}", callbackUrl);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "回调失败：{CallbackUrl}", callbackUrl);
                // 可在此处将失败回调存入重试队列
            }
        }


        public bool TryResponseIfSubscribe(JT808HeaderPackage headerPakcage)
        {
            if (IdAndJsonFactory.TryGetValue(headerPakcage.Header.MsgId, out var factory))
            {
                string id = factory(headerPakcage.Header.TerminalPhoneNo, out var _, out var response, headerPackage: headerPakcage);
                if (Callbacks.TryRemove(id, out var context))
                {
                    // 异步触发回调（不阻塞当前线程）
                    _ = TriggerCallbackAsync(context.CallbackUrl, response);
                }
                if (SubscribeIds.TryGetValue(id, out var status) && status != 2)
                {
                    Logger.LogInformation($"Try to produce a message with id={id}");
                    Producer.ProduceAsync(id, response);
                    SubscribeIds.TryUpdate(id, 1, status);
                    return true;
                }
                else
                    return false;
            }
            return false; // 消息未订阅
        }

        private void RegisterResponse(string id, in string callback)
        {
            SubscribeIds.AddOrUpdate(id, 0, (key, old) => 0);
            if (Uri.TryCreate(callback + $"/{id}", UriKind.Absolute, out var url))
            {
                CallbackContext context = new CallbackContext
                {
                    CallbackUrl = url,
                    ExpireTime = DateTimeOffset.Now.AddSeconds(options.Value.ResponseTimeout)
                };
                Callbacks.TryAdd(id, context);
            }
            var expiration = TimeSpan.FromSeconds(options.Value.ResponseTimeout);
            Task.Delay(expiration).ContinueWith(_ =>
            {
                SubscribeIds[id] = 2;
                Callbacks.TryRemove(id, out var _);
            });
        }

        public async Task<(bool success, string subscribeId)> TrySendIfOnline(JT808Package package, string callback = null)
        {
            string id = null;
            var success = await SessionManager.TrySendByTerminalPhoneNoAsync(package.Header.TerminalPhoneNo, JT808Serializer.Serialize(package));
            if (!success)
            {
                Logger.LogDebug($"Terminal whose sim={package.Header.TerminalPhoneNo} is offline");
            }
            else
            {
                if (options.Value.MsgHasResponse.TryGetValue("0x" + package.Header.MsgId.ToString("X4"), out var reply))
                {
                    if (IdAndJsonFactory.TryGetValue(Convert.ToUInt16(reply, 16), out var factory))
                    {
                        id = factory(package.Header.TerminalPhoneNo, out var _, out var _, package.Header.MsgId, package.Header.MsgNum);
                    }
                    else
                    {
                        id = package.Header.TerminalPhoneNo + package.Header.MsgId;
                    }
                    RegisterResponse(id, callback);
                }
            }
            return (success, id);
        }

        public bool TrySendIfOnline(in JT808Package package, out string id, in string callback = null)
        {
            id = null;
            if (!SessionManager.TrySendByTerminalPhoneNoAsync(package.Header.TerminalPhoneNo, JT808Serializer.Serialize(package)).Result)
            {
                return false;
            }
            else
            {
                if (options.Value.MsgHasResponse.TryGetValue("0x" + package.Header.MsgId.ToString("X4"), out var reply))
                {
                    if (IdAndJsonFactory.TryGetValue(Convert.ToUInt16(reply, 16), out var factory))
                    {
                        id = factory(package.Header.TerminalPhoneNo, out var _, out var _, package.Header.MsgId, package.Header.MsgNum);
                    }
                    else
                    {
                        id = package.Header.TerminalPhoneNo + package.Header.MsgId;
                    }
                    RegisterResponse(id, callback);
                }
                return true;
            }
        }
    }
}
